每天手動刷新租屋網站相當耗時。這篇文章將引導你使用自動化工具 n8n,建立一個專屬的通知機器人。它會每天定時抓取 591 租屋網的最新物件,篩選出符合你需求的房源,並自動發送到你的 Discord 頻道,讓你不再錯過任何一個理想中的家
來到儀表板,新增一個流程「Create Workflow」

初始節點選擇排程「On a schedule」

設定想收到通知的時間,比如說每日早上 9 點

接下來到「設定」裡面調整時區

把時區的「Timezone」設定為台灣時間

下個節點選「HTTP Request」,方法「GET」,URL 假如是以「台北、士林、雅房」的話可以這樣填寫
https://rent.591.com.tw/list?region=1§ion=8&kind=4&order=posttime&orderType=desc

下個節點選擇「HTML」的「Extract HTML Content」

填寫資料如下
Key: list
CSS Selector: .item-info
Return Value: HTML

下個節點選擇「Code」,程式碼如下
const htmlSnippets = $input.item.json.list;
// 如果沒有任何物件,就回傳一則提示訊息,並中止後續流程
if (!htmlSnippets || htmlSnippets.length === 0) {
  return { json: { content: "找不到任何租屋物件可以處理。" } };
}
// ---- Part 1: 篩選與解析資料 ----
const todayItems = [];
for (const html of htmlSnippets) {
  // 檢查是否為今天更新的物件
  const isUpdatedToday =
    html.includes("小時內更新") || html.includes("分鐘內更新");
  if (isUpdatedToday) {
    // 是今天更新的,才進行資料提取
    const titleMatch = html.match(
      /<a class="link v-middle"[^>]+title="([^"]+)"/
    );
    const urlMatch = html.match(/<a class="link v-middle" href="([^"]+)"/);
    const locationMatch = html.match(
      /<i class="house-(?:bus-line|metro|restaurant)[^>]*>[\s\S]*?<span[^>]*>([^<]+)<\/span><strong[^>]*>([^<]+)<\/strong>/
    );
    // 確保所有需要的資訊都成功抓取到
    if (titleMatch && urlMatch) {
      todayItems.push({
        title: titleMatch[1],
        url: urlMatch[1],
        location: locationMatch
          ? `${locationMatch[1].trim()}${locationMatch[2].trim()}`
          : "未提供",
      });
    }
  }
}
// ---- Part 2: 格式化為 Discord 訊息 ----
// 如果過濾後沒有今天更新的物件
if (todayItems.length === 0) {
  return { json: { content: `士林區在過去24小時內沒有新的租屋物件。` } };
}
const DISCORD_CHAR_LIMIT = 1900; // 保留一點緩衝
const todayDate = new Date().toLocaleDateString("zh-TW");
let message = `**【士林區今日新上架租屋 - ${todayDate}】**\n\n`;
for (const item of todayItems) {
  const itemString =
    `----------------------------------------\n` +
    `**標題:** ${item.title}\n` +
    `**地點:** ${item.location}\n` +
    `**網址:** ${item.url}\n\n`;
  // 檢查加上這筆資料後是否會超過字元上限
  if (message.length + itemString.length > DISCORD_CHAR_LIMIT) {
    message += `...還有更多物件,因訊息長度限制未完全顯示。`;
    break; // 超過則停止增加
  }
  message += itemString;
}
// ---- Part 3: 回傳最終結果 ----
// 回傳一個適合 Discord 節點使用的物件格式
return { json: { content: message } };
下個節點選擇 Discord 的「Send a message」

「Connection Type」選擇「Webhook」,憑證的串接在之前的文章有撰寫過,這邊就不重複惹

Message 直接填寫如下,接著點選「Execute step」來試跑看看
{
  {
    $json.content;
  }
}

接著會在 Discord 看到類似這樣的訊息,代表成功囉

完成後的畫布長這樣,記得要到上方切換為「Active」

底下也附上完整的 JSON 內容
{
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 9
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [0, 0],
      "id": "29e919e2-27b1-428f-a931-cf0f85d028e4",
      "name": "Schedule Trigger"
    },
    {
      "parameters": {
        "url": "https://rent.591.com.tw/list?region=1§ion=8&kind=4&order=posttime&orderType=desc",
        "options": {}
      },
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [220, 0],
      "id": "7fac6e24-5c3d-49ab-87a4-12b8fdab7e4c",
      "name": "HTTP Request"
    },
    {
      "parameters": {
        "operation": "extractHtmlContent",
        "extractionValues": {
          "values": [
            {
              "key": "list",
              "cssSelector": ".item-info",
              "returnValue": "html",
              "returnArray": true
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.html",
      "typeVersion": 1.2,
      "position": [440, 0],
      "id": "16b4a912-dc3c-4220-a8c0-06903c796ca1",
      "name": "HTML"
    },
    {
      "parameters": {
        "jsCode": "const htmlSnippets = $input.item.json.list;\n\n// 如果沒有任何物件,就回傳一則提示訊息,並中止後續流程\nif (!htmlSnippets || htmlSnippets.length === 0) {\n  return { json: { content: \"找不到任何租屋物件可以處理。\" } };\n}\n\n// ---- Part 1: 篩選與解析資料 ----\n\nconst todayItems = [];\nfor (const html of htmlSnippets) {\n  // 檢查是否為今天更新的物件\n  const isUpdatedToday = html.includes('小時內更新') || html.includes('分鐘內更新');\n\n  if (isUpdatedToday) {\n    // 是今天更新的,才進行資料提取\n    const titleMatch = html.match(/<a class=\"link v-middle\"[^>]+title=\"([^\"]+)\"/);\n    const urlMatch = html.match(/<a class=\"link v-middle\" href=\"([^\"]+)\"/);\n    const locationMatch = html.match(/<i class=\"house-(?:bus-line|metro|restaurant)[^>]*>[\\s\\S]*?<span[^>]*>([^<]+)<\\/span><strong[^>]*>([^<]+)<\\/strong>/);\n\n    // 確保所有需要的資訊都成功抓取到\n    if (titleMatch && urlMatch) {\n      todayItems.push({\n        title: titleMatch[1],\n        url: urlMatch[1],\n        location: locationMatch ? `${locationMatch[1].trim()}${locationMatch[2].trim()}` : '未提供'\n      });\n    }\n  }\n}\n\n// ---- Part 2: 格式化為 Discord 訊息 ----\n\n// 如果過濾後沒有今天更新的物件\nif (todayItems.length === 0) {\n  return { json: { content: `士林區在過去24小時內沒有新的租屋物件。` } };\n}\n\nconst DISCORD_CHAR_LIMIT = 1900; // 保留一點緩衝\nconst todayDate = new Date().toLocaleDateString('zh-TW');\nlet message = `**【士林區今日新上架租屋 - ${todayDate}】**\\n\\n`;\n\nfor (const item of todayItems) {\n  const itemString = `----------------------------------------\\n` +\n                     `**標題:** ${item.title}\\n` +\n                     `**地點:** ${item.location}\\n` +\n                     `**網址:** ${item.url}\\n\\n`;\n\n  // 檢查加上這筆資料後是否會超過字元上限\n  if (message.length + itemString.length > DISCORD_CHAR_LIMIT) {\n    message += `...還有更多物件,因訊息長度限制未完全顯示。`;\n    break; // 超過則停止增加\n  }\n\n  message += itemString;\n}\n\n// ---- Part 3: 回傳最終結果 ----\n\n// 回傳一個適合 Discord 節點使用的物件格式\nreturn { json: { content: message } };"
      },
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [660, 0],
      "id": "ca3e2c38-238b-47ac-89a7-09b77c6b6c6c",
      "name": "Code"
    },
    {
      "parameters": {
        "authentication": "webhook",
        "content": "={{ $json.content }}",
        "options": {}
      },
      "type": "n8n-nodes-base.discord",
      "typeVersion": 2,
      "position": [880, 0],
      "id": "48d0d745-9a37-43ff-bd95-b375123f25ea",
      "name": "Discord",
      "webhookId": "[REDACTED_WEBHOOK_ID]",
      "credentials": {}
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "HTTP Request",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTTP Request": {
      "main": [
        [
          {
            "node": "HTML",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "HTML": {
      "main": [
        [
          {
            "node": "Code",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Code": {
      "main": [
        [
          {
            "node": "Discord",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "meta": {
    "templateCredsSetupCompleted": true,
    "instanceId": "[REDACTED_INSTANCE_ID]"
  }
}